module IPFS.Client.Command.Files.Regular (
 ls,
 cat,
 catReadableStream,
 get,
 getReadableStream,
 LSInfo(..),
 ObjectInfo(..),
 LinkInfo(..),
 GetFileInfo(..),
 GetFileStreamInfo(..)
) where
import Prelude

import Data.Argonaut (class DecodeJson, Json, decodeJson, fromString)
import Data.Argonaut.Decode ((.:), (.:?))
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Data.Maybe (Maybe(..), maybe)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff, Canceler(..), Error, makeAff)
import Foreign.Object as O
import IPFS.Client (Client)
import IPFS.Client.Command (Command, defCommand, genQSObject)
import IPFS.Client.Command.Utils.TarStreamToObjects (tarStreamToObjects)
import IPFS.Client.Utils.SendRequest (execBufferCommand, execJsonCommand, execStreamBufferCommand)
import Node.Buffer (Buffer)
import Node.SteamEx.Readable as SR
import Node.Stream.Helper as SH
import Node.StreamEx.Types (Readable)

newtype LSInfo = LSInfo (Array ObjectInfo)

derive instance genericLSLinfo :: Generic LSInfo _
instance showLSInfo :: Show LSInfo where
  show s = genericShow s

instance decodeJsonLSInfo :: DecodeJson LSInfo where
  decodeJson::Json -> Either String LSInfo
  decodeJson json = do
   dJson  <- decodeJson json
   objects <- dJson .: "Objects"
   pure $ LSInfo objects


newtype ObjectInfo = ObjectInfo {
  hash::String,
  links::Array LinkInfo
}
derive instance genericObjectInfo :: Generic ObjectInfo _
instance showObjectInfo :: Show ObjectInfo where
  show s = genericShow s

instance decodeJsonObjectInfo :: DecodeJson ObjectInfo where
  decodeJson::Json -> Either String ObjectInfo
  decodeJson json = do
    dJson <- decodeJson json
    name  <- dJson  .: "Hash"
    links <- dJson .: "Links"
    pure $ ObjectInfo {hash:name , links:links  }


newtype LinkInfo = LinkInfo {
  name::String,
  hash::String,
  size::Int,
  typ::Int
}
derive instance genericLinkInfo :: Generic LinkInfo _
instance showLinkInfo :: Show LinkInfo where
  show s = genericShow s

instance decodeJsonStatInfo :: DecodeJson LinkInfo where
  decodeJson::Json -> Either String LinkInfo
  decodeJson json = do
    dJson <- decodeJson json
    name <- dJson .:? "Name"
    hash <- dJson .: "Hash"
    size <- dJson .: "Size"
    typ  <- dJson .: "Type"
    pure $ LinkInfo {name:maybe "" identity name,hash:hash,size:size,typ:typ}


lsCommand::String -> Maybe Boolean -> Maybe Boolean -> Maybe Boolean  -> Maybe Boolean  -> Command
lsCommand path headers resolveType size stream = defCommand { path = "ls",args = Just [fromString path] , qs = O.empty}

ls::Client -> String -> Maybe Boolean -> Maybe Boolean -> Maybe Boolean  -> Maybe Boolean -> Aff (Either String LSInfo)
ls client path headers resolveType size stream = execJsonCommand client $ lsCommand  path headers resolveType size stream

--------------------------------------cat-------------------------------------------
catCommand::String -> Boolean -> Maybe Int -> Maybe Int -> Command
catCommand path isBuffer offset length = defCommand {path = "cat",
                                                     args = Just [fromString path] ,
                                                     qs = genQSObject ["offset" /\ (show <$> offset) , "length" /\ (show <$> length)] O.empty,
                                                     isBuffer = isBuffer
                                                    }

cat::Client -> String -> Maybe Int -> Maybe Int -> Aff (Either String Buffer)
cat client path offset length = execBufferCommand client $ catCommand path true offset length

--------------------------------------cat-------------------------------------------
catReadableStream::Client -> String -> Maybe Int -> Maybe Int -> Aff (Either String (Readable Buffer))
catReadableStream client path offset length = execStreamBufferCommand client $ catCommand path false offset length

--------------------------------------getReadableStream-------------------------------------------
newtype GetFileStreamInfo = GetFileStreamInfo {
  path::String,
  content::Readable Buffer
}

getReadableStream::Client -> String -> Aff (Either String GetFileStreamInfo)
getReadableStream client path = do
    eStream::Either String (Readable Buffer) <- execStreamBufferCommand client $ getCommand path
    case eStream of
      Left  l -> pure $ Left l
      Right r -> do 
                 affV <- makeAff (\cb -> do
                         tarStreamToObjects r (\read -> do
                                                 SR.onData read (\o -> cb $ Right $ GetFileStreamInfo {path:o.path,content: o.content})
                                              )
                         pure $ Canceler (\e -> pure unit)
                    )
                 pure $ Right affV

--------------------------------------get-------------------------------------------
newtype GetFileInfo = GetFileInfo {
  path::String,
  content::Buffer
}

derive instance genericGetFileInfo :: Generic GetFileInfo _
instance showGetFileInfo :: Show GetFileInfo where
  show s = genericShow s

getCommand:: String -> Command
getCommand path = defCommand {path = "get" , args = Just [fromString path] ,qs = O.empty , isBuffer = false}

get::Client -> String -> Aff (Either String GetFileInfo)
get client path = do
  eStream <- getReadableStream client path
  case eStream of
   Left  l -> pure $ Left l 
   Right r -> do  
                v <- makeAff $ fileStreamInfo2Info r
                pure $ Right v
 where
  fileStreamInfo2Info::GetFileStreamInfo -> (Either Error GetFileInfo → Effect Unit) → Effect Canceler
  fileStreamInfo2Info (GetFileStreamInfo fInfo) cb = do
     cw <- SH.concatEX (\buf -> cb $ Right $ GetFileInfo {path:fInfo.path,content:buf} )
     SR.pipe fInfo.content cw Nothing
     pure $ Canceler (\e -> pure unit)